using System;
using System.Collections;
using System.Data;
using System.IO;
using System.Runtime.Serialization;
using System.Security.Permissions;
using Widgetsphere.Core.Util;

namespace Widgetsphere.Core.DataAccess
{
	[Serializable]
	public abstract partial class SubDomainBase : DataSet, ISubDomain, ISerializable
	{
		#region Member Variables

		[Flags]
		public enum ItemStateConstants
		{
			Detached = 1,
			Unchanged = 2,
			Added = 4,
			Deleted = 8,
			Modified = 16,
		}

		protected string _modifier;
		protected ArrayList _selectCommands;
		// Our webservice reference object, don't serialize this!!
		protected DataAccessService _dataAccessService = null;
		private string _identifier = Guid.NewGuid().ToString();
		private int _timeOut = 0;

		#endregion

		#region Properties

		public string Identifier
		{
			get { return _identifier; }
		}

		public virtual string Modifier
		{
			get { return this._modifier; }
			set { this._modifier = value; }
		}

		public virtual int TimeOut
		{
			get { return _timeOut; }
			set
			{
				if (value < 0)
					value = 0;
				_timeOut = value;
			}
		}

		#endregion

		#region Constructors
		/// <summary>
		/// Use this to run SelectCommands locally.
		/// </summary>
		public SubDomainBase(string modifier)
		{
			_modifier = modifier;
			_selectCommands = new ArrayList();
			if (!this.DirectConnect)
			{
				_dataAccessService = new DataAccessService();
				_dataAccessService.Url = this.DataAccessServiceUrl;
			}
			this.EnforceConstraints = true;
		}

		public SubDomainBase(DataSet dataset, string modifier)
			: this(modifier)
		{
			this.Merge(dataset, true, MissingSchemaAction.AddWithKey);
		}

		protected SubDomainBase()
		{
			this.EnforceConstraints = true;
		}

		#endregion

		#region Abstract Methods
		[Obsolete("This property no longer applies.")]
		protected abstract bool DirectConnect { get;}
		[Obsolete("This property no longer applies.")]
		protected abstract string DataAccessServiceUrl { get;}
		protected abstract void CreateStronglyTypedTables(DataSet ds);
		public abstract string ConnectionString { get;}
		protected internal abstract IBusinessCollection this[Enum collection] { get;}
		#endregion

		#region ISerializable members
		/// <summary>
		/// Protected constructor for support of custom Deserialization.
		/// </summary>
		/// <param name="info"></param>
		/// <param name="context"></param>
		/// 
		protected SubDomainBase(SerializationInfo info, StreamingContext context)
		{
			if (!this.DirectConnect)
			{
				_dataAccessService = new DataAccessService();
				_dataAccessService.Url = this.DataAccessServiceUrl;
			}

			this.EnforceConstraints = false;
			//Deserialize DataSet
			var ds = new DataSet();

			var xmlSchema = info.GetString("XmlSchema");
			using (var sr = new StringReader(xmlSchema))
			{
				ds.ReadXmlSchema(sr);
			}
			var xmlData = info.GetString("DataSet");
			using (var ms = StringHelper.StringToMemoryStream(xmlData))
			{
				ms.Position = 0;
				ds.ReadXml(ms, XmlReadMode.DiffGram);
			}
			var persistableEntities = (ArrayList)SerializeHelper.SoapDeserializeFromString(info.GetString("persistableEntities"));
			foreach (PersistableDomainCollectionBase pe in persistableEntities)
			{
				this.AddCollection(pe);
			}
			this.Merge(ds, false, System.Data.MissingSchemaAction.Error);

			//Deserialize Modifier
			_modifier = info.GetString("mModifier");

			//Deserialize Retrieve Rules
			_selectCommands = (ArrayList)info.GetValue("mSelectCommands", typeof(ArrayList));

			//Ensure constraints enforced
		}

		[SecurityPermission(SecurityAction.Demand, SerializationFormatter = false)]
		public override void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			//Serialize DataSet
			info.AddValue("XmlSchema", this.GetXmlSchema());
			using (var ms = new MemoryStream())
			{
				this.WriteXml(ms, XmlWriteMode.DiffGram);
				var xmlDataset = StringHelper.MemoryStreamToString(ms);
				//info.AddValue("DataSet", @"<![CDATA[<" + xmlDataset + @"]]>");
				info.AddValue("DataSet", xmlDataset);
			}
			var persistableEntities = new ArrayList();
			foreach (PersistableDomainCollectionBase pe in this.Tables)
			{
				persistableEntities.Add(pe);
			}
			info.AddValue("persistableEntities", SerializeHelper.SoapSerialize(persistableEntities));
			info.AddValue("enforceConstraints", this.EnforceConstraints);

			//Serialize Modifier
			info.AddValue("mModifier", this._modifier);

			//Serialize Retrieve Rules
			info.AddValue("mSelectCommands", _selectCommands);
		}
		#endregion

		#region DomainCollectionBase

		protected DomainCollectionBase this[string requestedCollection]
		{
			get
			{
				var dt = this.Tables[requestedCollection.ToString()];
				return (DomainCollectionBase)dt;
			}
		}

		#endregion

		#region ClearSelectCommands

		/// <summary>
		/// Remove all SelectCommands from internal collection.
		/// </summary>
		public virtual void ClearSelectCommands()
		{
			_selectCommands.Clear();
		}

		#endregion

		#region AddSelectCommand

		/// <summary>
		/// Add a SelectCommand to the internal collection.
		/// </summary>
		/// <param name="rule"></param>
		public virtual void AddSelectCommand(SelectCommand command)
		{
			if (_selectCommands == null)
				_selectCommands = new ArrayList();
			_selectCommands.Add(command);
		}

		#endregion

		#region Retrieve

		public virtual bool DisableAutomaticRetrieves { get; set; }
		
		/// <summary>
		/// Executes fill rules to produce a result
		/// </summary>
		public virtual void RunSelectCommands()
		{
			if (!this.DisableAutomaticRetrieves)
			{
				if (_dataAccessService == null)
				{
					RunRules();
				}
				else
				{
					System.Diagnostics.Debug.WriteLine("Go Through Web Service");
					try
					{
						var oldEc = this.EnforceConstraints;
						this.EnforceConstraints = false;
						var sdWrapper = _dataAccessService.Retrieve(new SubDomainWrapper(this));
						this.MergeNewDomain(sdWrapper.SubDomain);
						this.EnsureParentRowsExist();
						this._selectCommands.Clear();
						this.EnforceConstraints = oldEc;
					}
					catch (Exception ex)
					{
						System.Diagnostics.Debug.WriteLine(ex.ToString());
						throw;
					}
				}
			}
		}

		private void MergeNewDomain(DataSet ds)
		{
			this.CreateStronglyTypedTables(ds);
			this.Merge(ds, false, System.Data.MissingSchemaAction.Error);
		}
		#endregion

		#region RunRules

		private void RunRules()
		{
			try
			{
				var oldEc = this.EnforceConstraints;
				this.EnforceConstraints = false;
				foreach (SelectCommand rule in _selectCommands)
				{
					this.ExecuteRule(rule);
				}
				this.ClearSelectCommands();
				EnsureParentRowsExist();
				this.EnforceConstraints = oldEc;
			}
			catch (Exception ex)
			{
				//TODO: Make this fool proof giving good details.
				if (ex.Message.Contains("timeout period elapsed"))
					throw new Exception("The timeout period elapsed prior to completion of the operation or the server is not responding (Timeout = " + this.TimeOut + ").");
				else
					throw ex;
			}
			finally
			{
				this.ClearSelectCommands();
			}
		}

		#endregion

		#region ExecuteRule

		private void ExecuteRule(SelectCommand rule)
		{
			rule.TimeOut = rule.DefaultTimeOut;
			if (this.TimeOut > 0) rule.TimeOut = this.TimeOut;

			var domainCollection = rule.Execute(this);
			if (typeof(ReadOnlyDomainCollection).IsInstanceOfType(domainCollection))
			{
				this.AddCollection((ReadOnlyDomainCollection)domainCollection);
			}
			else
			{
				this.AddCollection((PersistableDomainCollectionBase)domainCollection);
			}
		}

		#endregion

		#region MergeDomainCollection

		private void MergeDomainCollection(DomainCollectionBase newDomainCollection)
		{
			var currentDomainCollection = this[""];
			this.Merge(newDomainCollection, false, MissingSchemaAction.Error);
		}

		#endregion

		#region AddCollection

		protected void AddCollection(PersistableDomainCollectionBase collection)
		{
			if (this.Tables.Contains(collection.TableName))
			{
				this.MergeDomainCollection(collection);				
			}
			else
			{
				this.Tables.Add(collection);
				collection.CreateRelations();
			}

			//Ensure that all relations are there
			foreach (PersistableDomainCollectionBase c in this.Tables)
			{
				c.CreateRelations();
			}

			if (collection.Rows.Count > 0)
			{
				((PersistableDomainCollectionBase)this.Tables[collection.TableName]).SetChildSelectedFalse();
			}

		}

		protected void AddCollection(ReadOnlyDomainCollection collection)
		{
			if (this.Tables.Contains(collection.TableName))
			{
				this.MergeDomainCollection(collection);
			}
			else
			{
				this.Tables.Add(collection);
			}
		}

		#endregion

		#region EnsureParentRowsExist
		protected bool EnsureParentExistsOn = true;

		public virtual void EnsureParentRowsExist()
		{
			if (EnsureParentExistsOn)
			{
				var oldEc = this.EnforceConstraints;
				try
				{
					this.EnforceConstraints = true;
				}
				catch (ConstraintException ex)
				{
					if (this.HasErrors)
					{
						this.HandleEnsureParentExistException(ex);
					}
					else
					{
						throw;
					}
				}
				finally
				{
					this.EnforceConstraints = oldEc;
				}
			}
		}

		#endregion

		#region HandleEnsureParentExistException

		private void HandleEnsureParentExistException(ConstraintException ex)
		{
			try
			{
				var errorsHandled = false;
				foreach (IDomainCollection idc in this.Tables)
				{
					if (idc != null)
					{
						if (typeof(PersistableDomainCollectionBase).IsInstanceOfType(idc))
						{
							var pe = (PersistableDomainCollectionBase)idc;
							if ((pe != null) && pe.HasErrors)
							{
								if (pe.HandleErrors())
								{
									errorsHandled = true;
								}
							}
						}
					}
				}
				if (errorsHandled)
				{
					this.RunSelectCommands();
				}
				else
				{
					throw ex;
				}
			}
			catch (Exception ex1)
			{
				throw ex1;
			}
		}

		#endregion

		#region RunRulesFromWebService

		/// <summary>
		/// Called from webservice webmethod to run SelectCommands and return the results.
		/// </summary>
		/// <returns></returns>
		public virtual SubDomainBase RunRulesFromWebService()
		{
			this.RunRules();
			return this;
		}

		#endregion

		#region RunPersist

		private void RunPersist()
		{
			if (this.Tables.Count > 0)
			{
				((PersistableDomainCollectionBase)this.Tables[0]).FullUpdate();
			}
		}

		#endregion

		#region Persist

		public virtual void Persist()
		{
			if (_dataAccessService == null)
			{
				RunPersist();
			}
			else
			{
				System.Diagnostics.Debug.WriteLine("Go Through Web Service");
				try
				{
					var oldEc = this.EnforceConstraints;
					this.EnforceConstraints = false;
					var sdWrapper = _dataAccessService.Persist(new SubDomainWrapper(this));
					//this.Clear();
					this.AcceptChanges();
					this.MergeNewDomain(sdWrapper.SubDomain);
					this.EnforceConstraints = oldEc;
				}
				catch (Exception ex)
				{
					System.Diagnostics.Debug.WriteLine(ex.ToString());
					throw;
				}
			}
		}

		#endregion

		#region File Operations

		public virtual void SaveToFile(string file)
		{
			SaveToFile(file, true);
		}

		public virtual void SaveToFile(string file, bool overwrite)
		{
			if (File.Exists(file) && overwrite)
			{
				File.Delete(file);
			}
			else if (File.Exists(file))
			{
				throw new Widgetsphere.Core.Exceptions.WSException("File already exists");
			}
			SerializeHelper.SoapSerialize(file, this);
		}

		public static SubDomainBase LoadFromFile(string file)
		{
			if (File.Exists(file))
			{
				return (SubDomainBase)SerializeHelper.SoapDeserialize(file);
			}
			else
			{
				throw new Widgetsphere.Core.Exceptions.WSException("File does not exist");
			}
		}

		#endregion

		#region IsDirty

		public virtual bool IsDirty
		{
			get { return this.HasChanges(); }
		}

		#endregion

		#region Set Operations

		private DataTable SetupSetOperationDataSet(DataSet dataset, string key)
		{
			//There must be a table
			if (dataset.Tables.Count != 1)
				throw new ApplicationException("There must be one datatable present to perform this operation.");

			//The Key must exist
			var dataTable = dataset.Tables[0];
			if (!dataTable.Columns.Contains(key))
				throw new ApplicationException("There specified column does not exist in the table.");

			//If there is no primary key on the table then create one
			if (dataTable.PrimaryKey.Length == 0)
			{
				DataColumn[] primaryKeys = { dataTable.Columns[key] };
				dataTable.PrimaryKey = primaryKeys;
			}
			else
			{
				//If the primary key is not the specified key then drop 
				//it and create a new primary key as the specified key
				if (!Widgetsphere.Core.Util.StringHelper.Match(dataTable.PrimaryKey[0].ColumnName, key, true))
				{
					DataColumn[] primaryKeys = { dataTable.Columns[key] };
					dataTable.PrimaryKey = primaryKeys;
				}
			}

			return dataTable;
		}

		public virtual DataSet Union(DataSet dataset, string key)
		{
			//Create a temp dataset
			var retval = new DataSet();
			retval.Merge(this, true, MissingSchemaAction.AddWithKey);

			//Get the 2 datatables
			var dataTable1 = SetupSetOperationDataSet(retval, key);
			var dataTable2 = SetupSetOperationDataSet(dataset, key);

			retval.Merge(dataset, true, MissingSchemaAction.AddWithKey);
			ResetRank(retval);

			return retval;
		}

		public virtual DataSet Intersection(DataSet dataset, string key)
		{
			//Create a temp dataset
			var retval = new DataSet();
			retval.Merge(this, true, MissingSchemaAction.AddWithKey);

			//Get the 2 datatables
			var dataTable1 = SetupSetOperationDataSet(retval, key);
			var dataTable2 = SetupSetOperationDataSet(dataset, key);

			for (var ii = dataTable2.Rows.Count - 1; ii >= 0; ii--)
			{
				//Current row
				var dr = dataTable2.Rows[ii];

				var hasMatch = false;
				if ((dataTable1.Columns[key].DataType == typeof(System.String)) || (dataTable1.Columns[key].DataType == typeof(System.Guid)) || (dataTable1.Columns[key].DataType == typeof(System.DateTime)))
					hasMatch = (dataTable1.Select(key + " = '" + (string)dr[key] + "'").Length > 0);
				else
					hasMatch = (dataTable1.Select(key + " = " + (string)dr[key]).Length > 0);

				//If there is no match then remove it since there is no intersection
				if (!hasMatch)
					dataTable1.Rows.Remove(dr);
			}
			ResetRank(retval);

			return retval;
		}

		public virtual DataSet Subtract(DataSet dataset, string key)
		{
			//Create a temp dataset
			var retval = new DataSet();
			retval.Merge(this, true, MissingSchemaAction.AddWithKey);

			//Get the 2 datatables
			var dataTable1 = SetupSetOperationDataSet(retval, key);
			var dataTable2 = SetupSetOperationDataSet(dataset, key);

			for (var ii = dataTable2.Rows.Count - 1; ii >= 0; ii--)
			{
				//Current row
				var dr = dataTable2.Rows[ii];

				DataRow[] rows;
				if ((dataTable1.Columns[key].DataType == typeof(System.String)) || (dataTable1.Columns[key].DataType == typeof(System.DateTime)) || (dataTable1.Columns[key].DataType == typeof(System.Guid)))
					rows = dataTable1.Select(key + " = '" + dr[key].ToString() + "'");
				else
					rows = dataTable1.Select(key + " = " + dr[key].ToString());

				//If there is a match then remove it for subtraction
				foreach (var loopRow in rows)
					dataTable1.Rows.Remove(loopRow);
			}
			ResetRank(retval);


			return retval;
		}

		private void ResetRank(DataSet ds)
		{
			//Reset the RankOrder in this new dataset
			var ii = 1;
			foreach (DataRow dr in ds.Tables[0].Rows)
			{
				dr["rankorder"] = ii;
				ii++;
			}
		}

		#endregion

	}
}
